package com.googlecode.jsqlconverter.service;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;

import com.googlecode.jsqlconverter.annotation.ServiceName;
import com.googlecode.jsqlconverter.filter.Filter;
import com.googlecode.jsqlconverter.parser.Parser;
import com.googlecode.jsqlconverter.producer.Producer;

public class Service {
	private ArrayList<EntryPoint> entryPointList = new ArrayList<EntryPoint>();
	private Class<?> serviceClass;
	private String name;
	private String flattenedName;
	private ServiceType type;

	public Service(Class<?> serviceClass) {
		ServiceName a = serviceClass.getAnnotation(ServiceName.class);

		if (a == null) {
			throw new RuntimeException("ServiceName not defined for " + serviceClass.getName());
		}

		this.serviceClass = serviceClass;
		name = a.value();
		flattenedName = name.toLowerCase().replace(" ", "-");
		type=determineType(serviceClass);

		Constructor<?>[] cs = serviceClass.getConstructors();

		for (Constructor<?> c : cs) {
			entryPointList.add(new EntryPoint(c));
		}
	}

	private ServiceType determineType(Class<?> serviceClass2) {
		if  (Parser.class.isAssignableFrom(serviceClass)) return ServiceType.Parser;
		if  (Producer.class.isAssignableFrom(serviceClass)) return ServiceType.Producer;
		if  (Filter.class.isAssignableFrom(serviceClass)) return ServiceType.Filter;
		return null;
	}

	public String getName() {
		return name;
	}

	public String getFlattenedName() {
		return flattenedName;
	}

	public ServiceType getServiceType() {
		return type;
	}

	public Object newInstance(SimpleEntry<Parameter, Object>[] parameters) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		ArrayList<Class<?>> classParams = new ArrayList<Class<?>>();
		ArrayList<Object> values=new ArrayList<Object>();

		for (SimpleEntry<Parameter, Object> obj : parameters) {
			classParams.add(obj.getKey().getClassType());
			values.add(obj.getValue());
		}
		

		return serviceClass.getConstructor(
			classParams.toArray(new Class[classParams.size()])
		).newInstance(values.toArray());
	}

	public EntryPoint[] getEntryPoints() {
		return entryPointList.toArray(new EntryPoint[entryPointList.size()]);
	}

	public SimpleEntry<Parameter, Object>[] getMatchingConstructorArguments(HashMap<String, Object> argMap) throws FileNotFoundException {
		EntryPoint matchedEp = null;
		ArrayList<SimpleEntry<Parameter, Object>> finalArgs = new ArrayList<SimpleEntry<Parameter, Object>>();
		boolean matchAll;
		Parameter returnPrintStream = null;

		for (EntryPoint ep : entryPointList) {
			matchAll = true;
			returnPrintStream = null;

			for (Parameter p : ep.getParameters()) {
				if (!argMap.containsKey(p.getFlattenedName()) && !p.isOptional()) {
					if (p.getClassType().equals(PrintStream.class)) {
						returnPrintStream = p;
						continue;
					}

					matchAll = false;
					break;
				}
			}

			if (matchAll) {
				matchedEp = ep;
				break;
			}
		}

		if (returnPrintStream!=null) {
			return new SimpleEntry[] { new SimpleEntry(returnPrintStream,System.out) };
		}

		if (matchedEp == null) {
			throw new RuntimeException("No matching EntryPoint found");
		}

		// match constructor order and set defaults
		for (Parameter p : matchedEp.getParameters()) {
			if (argMap.containsKey(p.getFlattenedName())) {
				finalArgs.add(new SimpleEntry<Parameter, Object>(p,p.toObject(argMap.get(p.getFlattenedName()))));
			} else if (p.isOptional()) {
				finalArgs.add(new SimpleEntry<Parameter, Object>(p,p.toObject(p.getDefaultValue())));
			} else {
				throw new RuntimeException("Argment matching, this should never happen");
			}
		}

		return finalArgs.toArray(new SimpleEntry[finalArgs.size()]);
	}
}
